﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BoatNGoat.Logic.Map.Generator
{

    /// <summary>
    /// Generates a map using the Diamond-Square fractal algorithm
    /// </summary>
    class DiamondSquareFractalTerrain : IHeightFunction
    {
        private readonly double[,] _fractalHeights;
        private const double Roughness = 4.0;

        /// <summary>
        /// Construct a width x height fractal terrain.
        /// </summary>
        /// <param name="width">The width of the terrain generated</param>
        /// <param name="height">The height of the terrain generated</param>
        public DiamondSquareFractalTerrain(int width, int height)
        {
            _fractalHeights = GenerateFractal(width-1, height-1, Roughness);
        }


        public double[,] GenerateFractal(int width, int height, double roughness)
        {
            double[,] points = new double[width+1, height+1];
            
            //Assign the four corners of the intial grid random color values
            //These will end up being the colors of the four corners of the applet.		
            double c1 = Rnd.NextDouble();
            double c2 = Rnd.NextDouble();
            double c3 = Rnd.NextDouble();
            double c4 = Rnd.NextDouble();

            DivideGrid(ref points, 0, 0, width, height, c1, c2, c3, c4, roughness);
            return points;
        }

        // I'll be honest, I ripped this off the internet. Easy to understand, annoying to write.
        private void DivideGrid(ref double[,] points, double x, double y, double width, double height, double c1, double c2, double c3, double c4, double roughness)
        {
            double bigSize = width + height;

            double newWidth = Math.Floor(width / 2);
            double newHeight = Math.Floor(height / 2);

            if (width > 1 || height > 1)
            {
                double middle = ((c1 + c2 + c3 + c4) / 4) + Displace(newWidth + newHeight, bigSize, roughness);
                double edge1 = ((c1 + c2) / 2);
                double edge2 = ((c2 + c3) / 2);
                double edge3 = ((c3 + c4) / 2);
                double edge4 = ((c4 + c1) / 2);

                //Make sure that the midpoint doesn't accidentally get randomly displaced past the boundaries
                middle= Rectify(middle);
                edge1 = Rectify(edge1);
                edge2 = Rectify(edge2);
                edge3 = Rectify(edge3);
                edge4 = Rectify(edge4);

                //Do the operation over again for each of the four new grids.			
                DivideGrid(ref points, x, y, newWidth, newHeight, c1, edge1, middle, edge4, roughness);
                DivideGrid(ref points, x + newWidth, y, width - newWidth, newHeight, edge1, c2, edge2, middle, roughness);
                DivideGrid(ref points, x + newWidth, y + newHeight, width - newWidth, height - newHeight, middle, edge2, c3, edge3, roughness);
                DivideGrid(ref points, x, y + newHeight, newWidth, height - newHeight, edge4, middle, edge3, c4, roughness);
            }
            else	//This is the "base case," where each grid piece is less than the size of a pixel.
            {
                //The four corners of the grid piece will be averaged and drawn as a single pixel.
                double c = (c1 + c2 + c3 + c4) / 4;

                points[(int)(x), (int)(y)] = c;
                if (width == 2)
                {
                    points[(int)(x+1), (int)(y)] = c;
                }
                if (height == 2)
                {
                    points[(int)(x), (int)(y+1)] = c;
                }
                if ((width == 2) && (height == 2)) 
                {
                    points[(int)(x + 1), (int)(y+1)] = c;
                }
            }
        }

        private double Rectify(double iNum)
        {
            if (iNum < 0)
            {
                iNum = 0;
            }
            else if (iNum > 1.0)
            {
                iNum = 1.0;
            }
            return iNum;
        }

        private double Displace(double smallSize, double bigSize, double roughness)
        {
            
            double max = smallSize/ bigSize * roughness;
            return (Rnd.NextDouble() - 0.5) * max;
        }

        public double Z(int x, int y)
        {
            return (2.0 * _fractalHeights[x, y]) - 1;
        }
    }

}
